﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;

using ettaingroup.data.ef.Interfaces;
using ettaingroup.data.ef.Enums;
using System.Configuration;
using System.Data.Entity.Validation;
using System.Data.Entity.Infrastructure;
using System.Transactions;

namespace ettaingroup.data.ef
{
    /// <summary>
    /// For items that require more than single entity operations, the GenericUnitOfWork provides a way
    /// to chain DataActions together to perform work on the same context.  A typical interaction would be to
    /// create the GenericUnitOfWork, add IDataActions to it and the call the Save method (which performs the
    /// actions in the order they were added all on the same context, and then call SaveChanges on the context
    /// (making the GenericRepositories pass-through the context).
    /// </summary>
    public class UnitOfWork
    {
        private bool Validate { get; set; }
        /// <summary>
        /// The list of actions to perform
        /// </summary>
        public List<IDataAction> Actions { get; set; }
        /// <summary>
        /// the context in which to perform DataActions specfied in the Actions list
        /// </summary>
        public DbContext Context { get; set; }

        public TransactionScope TransactionScope { get; set; }

        /// <summary>
        /// Default constructor, creating a new context
        /// </summary>
        public UnitOfWork()
        {
            Context = new DbContext(ConfigurationManager.AppSettings["EntityFrameworkGenericRepository_ConnectionString"]);
            Actions = new List<IDataAction>();
        }
        public UnitOfWork(bool validateEntitiesOnSave)
        {
            Context = new DbContext(ConfigurationManager.AppSettings["EntityFrameworkGenericRepository_ConnectionString"]);
            Actions = new List<IDataAction>();
            Context.Configuration.ValidateOnSaveEnabled = validateEntitiesOnSave;
            Validate = validateEntitiesOnSave;
        }
        /// <summary>
        /// Constructor specifying the DbContext to use
        /// </summary>
        /// <param name="ctx">The DbContext to use</param>
        public UnitOfWork(DbContext ctx)
        {
            Context = ctx;
            Actions = new List<IDataAction>();
        }
        public UnitOfWork(DbContext ctx, bool validateEntitiesOnSave)
        {
            Context = ctx;
            Context.Configuration.ValidateOnSaveEnabled = validateEntitiesOnSave;
            Actions = new List<IDataAction>();
        }
        public UnitOfWork(bool validateEntitiesOnSave, bool useDiscreteRepositories)
        {
            if (useDiscreteRepositories)
            {
                Context = null;
            }
            else
            {
                Context = new DbContext(ConfigurationManager.AppSettings["EntityFrameworkGenericRepository_ConnectionString"]);
                
                Validate = validateEntitiesOnSave;
                Context.Configuration.ValidateOnSaveEnabled = Validate;
            }
            Actions = new List<IDataAction>();

        }
        public UnitOfWork(TransactionScope trans)
        {
            this.TransactionScope = trans;
            Context = new DbContext(ConfigurationManager.AppSettings["EntityFrameworkGenericRepository_ConnectionString"]);
            Actions = new List<IDataAction>();
        } 
        public UnitOfWork(TransactionScope trans, DbContext context)
        {
            this.TransactionScope = trans;
            this.Context = context;
            Actions = new List<IDataAction>();
        }
        /// <summary>
        /// Adds DataAction to the Actions list
        /// </summary>
        /// <typeparam name="T">The Type of entity</typeparam>
        /// <param name="entity">The actual entity on which to perform the action</param>
        /// <param name="action">The action to perform</param>
        public void AddAction<T>(T entity, DataActions action) where T : class
        {
            if (Context != null)
            {
                DataAction<T> act = new DataAction<T>(Context, entity, action);
                this.Actions.Add(act);
            }
            else
            {
                DataAction<T> act = new DataAction<T>(entity, action, this.Validate);
                this.Actions.Add(act);
            }
        }
        /// <summary>
        /// Peforms all of the actions specified in the Actions list in the order they were added and calls SaveChanges() on the context
        /// we assume that this.TransactionScope will be part of a using external to this object.  If you pass it in, it's up to you to
        /// dispose of it.
        /// </summary>
        public void Save()
        {
            TransactionScope transaction = this.TransactionScope ?? new TransactionScope();
            
            try
            {
                foreach (var action in Actions)
                {
                    action.Save();
                }
                if (this.Context != null)
                {
                    this.Context.SaveChanges();
                }
				if (this.TransactionScope == null)
					transaction.Complete();
            }
            catch (DbEntityValidationException ex)
            {
                throw new RepositoryException(ex.Message, ex);
            }
            catch (DbUpdateException ex)
            {
                throw new RepositoryException(ex.InnerException.InnerException != null ? ex.InnerException.InnerException.Message : ex.InnerException.Message, ex);
            }
            catch (Exception ex)
            {
                throw new RepositoryException(ex.Message, ex);
            }
            
            finally 
            {
                if (this.TransactionScope == null)
                {
                    transaction.Dispose();
                }
            }
        }
    }
}
